Skip to content

Validate a bid coming from the Builder API (Gloas) - #11260

Closed
StefanBratanov wants to merge 4 commits into
Consensys-Incorporated:masterfrom
StefanBratanov:bid_validation_builder
Closed

StefanBratanov wants to merge 4 commits into
Consensys-Incorporated:masterfrom
StefanBratanov:bid_validation_builder

Conversation

@StefanBratanov

@StefanBratanov StefanBratanov commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

PR Description

Implements the rules as per https://github.com/ethereum/builder-specs/blob/main/specs/gloas/validator.md#validating-a-signedexecutionpayloadbid and filters out any bids which fail validation. (warn message is logged in this case)

Fixed Issue(s)

fixes #11191

Documentation

  • I thought about documentation and added the doc-change-required label to this PR if updates are required.

Changelog

  • I thought about adding a changelog entry, and added one if I deemed necessary.

Note

Medium Risk
Changes which builder bids are accepted for block production and aligns on-chain bid signature checks with the configured BLS verifier; incorrect validation could reject valid bids or accept invalid ones, though coverage is added in unit tests.

Overview
Adds Gloas Builder API bid validation so execution payload bids from configured builders are checked against the validator spec before use. Invalid bids are dropped with warn logs.

Introduces BuilderBidValidator (active builder, slot, parent hash/root, prev_randao, optional proposer fee recipient and gas-limit compatibility, collateral, BLS signature). BuilderBidFetcher runs this after each HTTP fetch and short-circuits when no builders are configured. BeaconChainController wires the validator into the fetcher.

Signature verification now uses the spec’s getBLSSignatureVerifier() in Gloas block processing and in bid validation (replacing BLSSignatureVerifier.SIMPLE in BlockProcessorGloas). ExecutionPayloadBidSelector also applies min_bid to builder API bids. isGasLimitTargetCompatible is shared publicly from gossip validation for proposer-preference checks.

Reviewed by Cursor Bugbot for commit 9b822b4. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@gfukushima

Copy link
Copy Markdown
Contributor

reviewed this PR myself and it looked alright to me, but ran a sanity check on claude and it came back with a few things:

Correctness

1. Fail-open when proposer preferences are missing (BuilderBidValidator.java:99). If the in-memory preferences map has no entry for the slot, the fee-recipient and gas-limit checks are skipped and the bid is accepted. The builder-specs validate_bid asserts fee recipient unconditionally, and the gossip validator saves-for-future in the same case. A builder could set its own address as fee recipient and get paid via the pending withdrawal. The test skipsFeeAndGasLimitChecksWhenProposerPreferencesAbsent pins this behaviour as intended, which is probably wrong.
2. Parent block hash never compared to the requested parent (BuilderBidValidator.java:80). The bid is accepted if its parent matches either the FULL or EMPTY variant, and ExecutionPayloadBidSelector only applies the parent-hash filter to p2p bids. A builder bid built on the wrong variant can win selection and fail state transition on the execution requests root, causing a missed proposal. Fix by adding the same filter to the builder stream or passing the parent hash into validateBid.
3. Three cheap checks omitted that the block processor enforces: builder version equals PAYLOAD_BUILDER_VERSION, block hash differs from parent block hash, and blob commitment count within the max. Spec-faithful omission, but since builder bids bypass gossip validation, nothing else catches them before the proposer's own block is rejected.
4. Untrusted bid slot used before the slot check (BuilderBidValidator.java:63). Predicates and accessors resolve from bid.getSlot() before slot equality is verified. A pre-Gloas slot in the response throws IllegalArgumentException instead of a clean rejection.
5. Missing parent gas limit throws instead of returning false (BuilderBidValidator.java:111). After checkpoint sync at a Gloas slot the gas limit can be unavailable for a while, producing a stack-trace WARN per builder per slot. Should log a one-line reason and return false like every other check.

Test coverage

6. rejectsIfGasLimitNotCompatibleWithProposerPreferences uses different random addresses for the bid and preferences, so it rejects on fee-recipient mismatch first. The gas-limit branch has zero effective coverage. Deleting it leaves the suite green.
7. The new minBidPredicate filter on the builder-bid stream in ExecutionPayloadBidSelector has no test.

Design

8. The new class duplicates five checks already exposed by GossipValidationHelper in the same module, and its simple name collides with the existing MEV-boost BuilderBidValidator interface. Injecting the helper and renaming to something like ExecutionPayloadBidBuilderApiValidator would avoid a third copy that drifts, which finding 3 already demonstrates.

I was keen to collab on this PR or a next one if you don't mind

@StefanBratanov

Copy link
Copy Markdown
Contributor Author

@gfukushima feel free to push to this PR with the changes suggested, I wouldn't work on it if you want?

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9b822b4. Configure here.

LOG.warn("Bid rejected: gas limit {} is not compatible with target", bid.getGasLimit());
return false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing preferences skip fee checks

High Severity

When proposerPreferencesManager has no entry for the slot, validateBid skips both the fee_recipient and gas-limit checks and still accepts the bid. Builder-specs validate_bid asserts those fields unconditionally, and the gossip path defers instead of accepting. A builder can then win selection with the wrong fee recipient, and the proposer includes that bid.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9b822b4. Configure here.

&& !bid.getParentBlockHash().equals(stateGloas.getLatestBlockHash())) {
LOG.warn("Bid rejected: parent block hash does not extend a known parent");
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong-parent builder bids accepted

Medium Severity

validateBid accepts a parent hash matching either latest_execution_payload_bid.block_hash or latest_block_hash, and neither the fetcher nor the selector requires the bid to match the production parentHash/parentRoot. After an empty slot those hashes diverge, so a high-value bid on the unrevealed payload can be selected and then fail process_execution_payload_bid.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9b822b4. Configure here.

@StefanBratanov
StefanBratanov marked this pull request as draft September 14, 2026 09:02
@StefanBratanov

Copy link
Copy Markdown
Contributor Author

Close in favour of #11279

@github-actions github-actions Bot locked and limited conversation to collaborators Sep 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate a bid coming from an external builder

2 participants